/* Matías Blanco & Sergio Pérez      */
/* Cintas Transportadoras. Práctica 4 - Monitores en Java */

package cintasjava;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Random;

enum TEstadoMotor{Parado, Izquierda, Derecha, Estropeado};

public class Main {
    final static int MAXPERSONAS = 50;
    
    public static void main(String[] args) {
        // Definimos los objetos que vamos a manejar
        TipoMotor Motor1 = new TipoMotor(1);
        TipoMotor Motor2 = new TipoMotor(2);
        TipoComprobadorEstadoMotor ComprobadorEstadoMotor = new TipoComprobadorEstadoMotor(Motor1,Motor2);
        TipoSensor_Der Sensor1_Der = new TipoSensor_Der(Motor1,ComprobadorEstadoMotor);
        TipoSensor_Izq Sensor1_Izq = new TipoSensor_Izq(Motor1,ComprobadorEstadoMotor);
        TipoSensor_Der Sensor2_Der = new TipoSensor_Der(Motor2,ComprobadorEstadoMotor);
        TipoSensor_Izq Sensor2_Izq = new TipoSensor_Izq(Motor2,ComprobadorEstadoMotor);
        // Definimos y ejecutamos las tareas
        TipoReloj Reloj=new TipoReloj(Motor1,Motor2);
        TipoMecanico Mecanico = new TipoMecanico(Motor1,Motor2);
        TipoEstropearMotor EstropearMotor = new TipoEstropearMotor(Motor1,Motor2);
        // Lanzamos las tareas
        Reloj.start();
        Mecanico.start();
        EstropearMotor.start();
        // Creamos y lanzamos las tareas que representan a las personas
        TipoPersona Persona;
        System.out.println("---- COMIENZAN A SALIR PERSONAS");System.out.println();
        for(int i=0; i < MAXPERSONAS; i++){
            Persona = new TipoPersona(i+1,Sensor1_Der,Sensor1_Izq,Sensor2_Der,Sensor2_Izq);
            Persona.start();
        }
    }
}

class TipoMotor{
    private int id;
    private int nPersonas  = 0;
    private int Contador = 0;
    private TEstadoMotor Estado=TEstadoMotor.Parado;
    
    public TipoMotor(int id){
        this.id=id;
    }
    
    public int getId(){
        return id;
    }
    
    public synchronized void EntrarDerecha(){
        Estado = TEstadoMotor.Derecha;
        nPersonas ++;
    }
    
    public synchronized void EntrarIzquierda(){
        Estado = TEstadoMotor.Izquierda;
        nPersonas ++;
    }
    
    public synchronized void Salir(){
        nPersonas --;
    }
    
    public synchronized void Tiempo(){
        if (nPersonas == 0 && Estado != TEstadoMotor.Parado && Estado != TEstadoMotor.Estropeado){
            if (Contador == 60){
                Estado = TEstadoMotor.Parado;
                System.out.println("-- SE HA PARADO EL MOTOR"+id+" --");
            }else{
                Contador ++;
            }
        }else{
            Contador = 0;
        }
    }
    
    public synchronized void Estropear(){
        Estado = TEstadoMotor.Estropeado;
        System.out.println("??? SE HA ESTROPEADO EL MOTOR"+id+" ???");
    }
    
    public TEstadoMotor getEstado(){
        return Estado;
    }
    
    public synchronized void setEstado(TEstadoMotor Estado){
        this.Estado=Estado;
    }
}

class TipoComprobadorEstadoMotor{
    private TipoMotor Motor1;
    private TipoMotor Motor2;
    
    public TipoComprobadorEstadoMotor(TipoMotor Motor1, TipoMotor Motor2){
        this.Motor1=Motor1;
        this.Motor2=Motor2;
    }
    
    public synchronized TEstadoMotor DarEstado(int idMotor, TEstadoMotor EstadoSolicitado){
        TEstadoMotor Estado = null;
        if (idMotor == 1) {
            // Si el motor en el estado que se solicita o esta parado. Lo dejamos en el estado solicitado para reservarlo.
            // Y el otro motor no esta girando al mismo sentido que hemos solicitado
            if ((Motor1.getEstado() == EstadoSolicitado || Motor1.getEstado() == TEstadoMotor.Parado) && Motor2.getEstado() != EstadoSolicitado){
                if (Motor1.getEstado() == TEstadoMotor.Parado)
                    System.out.println("++ SE HA ACTIVADO EL MOTOR"+idMotor+" ++");
                Motor1.setEstado(EstadoSolicitado);
            }
            Estado = Motor1.getEstado();
        }else if(idMotor == 2){
            // Si el motor en el estado que se solicita o esta parado. Lo dejamos en el estado solicitado para reservarlo.
            // Y el otro motor no esta girando al mismo sentido que hemos solicitado
            if ((Motor2.getEstado() == EstadoSolicitado || Motor2.getEstado() == TEstadoMotor.Parado) && Motor1.getEstado() != EstadoSolicitado){
                if (Motor2.getEstado() == TEstadoMotor.Parado)
                    System.out.println("++ SE HA ACTIVADO EL MOTOR"+idMotor+" ++");
                Motor2.setEstado(EstadoSolicitado);
            }
            Estado = Motor2.getEstado();
        }
        
        return Estado;
    }
}

class TipoSensor_Der{
    private TipoMotor Motor;
    private TipoComprobadorEstadoMotor ComprobadorEstadoMotor;
    
    public TipoSensor_Der(TipoMotor Motor, TipoComprobadorEstadoMotor ComprobadorEstadoMotor){
        this.Motor=Motor;
        this.ComprobadorEstadoMotor=ComprobadorEstadoMotor;
    }
    
    public synchronized boolean EntrarPersona(){
        TEstadoMotor estadoActual;
        boolean pasar=false;
        // Miramos el estado del motor
        estadoActual = ComprobadorEstadoMotor.DarEstado(Motor.getId(), TEstadoMotor.Derecha);
        if (estadoActual == TEstadoMotor.Derecha) {
            Motor.EntrarDerecha();
            pasar = true;
        }
        return pasar;
    }
    
    public synchronized void SalirPersona(){
        Motor.Salir();
    }
}

class TipoSensor_Izq{
    private TipoMotor Motor;
    private TipoComprobadorEstadoMotor ComprobadorEstadoMotor;
    
    public TipoSensor_Izq(TipoMotor Motor, TipoComprobadorEstadoMotor ComprobadorEstadoMotor){
        this.Motor=Motor;
        this.ComprobadorEstadoMotor=ComprobadorEstadoMotor;
    }
    
    public synchronized boolean EntrarPersona(){
        TEstadoMotor estadoActual;
        boolean pasar=false;
        estadoActual = ComprobadorEstadoMotor.DarEstado(Motor.getId(), TEstadoMotor.Izquierda);
        if (estadoActual == TEstadoMotor.Izquierda) {
            Motor.EntrarIzquierda();
            pasar = true;
        }
        return pasar;
    }
    
    public synchronized void SalirPersona(){
        Motor.Salir();
    }
}


class TipoReloj extends Thread{
    private TipoMotor Motor1;
    private TipoMotor Motor2;
    
    public TipoReloj(TipoMotor Motor1, TipoMotor Motor2){
        this.Motor1=Motor1;
        this.Motor2=Motor2;
    }
    
    @Override
    public void run(){
        while(true){
            try {
                Motor1.Tiempo();
                Motor2.Tiempo();
                sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(TipoReloj.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
class TipoMecanico extends Thread{
    private TipoMotor Motor1;
    private TipoMotor Motor2;
    
    public TipoMecanico(TipoMotor Motor1, TipoMotor Motor2){
        this.Motor1=Motor1;
        this.Motor2=Motor2;
    }
    
    @Override
    public void run(){
        while(true){
            try {
                if (Motor1.getEstado()==TEstadoMotor.Estropeado){
                    sleep(3000); // tiempo de arreglar el motor
                    Motor1.setEstado(TEstadoMotor.Parado);
                    System.out.println("%%%% SE HA ARREGLADO EL MOTOR 1 %%%%");
                }
                if (Motor2.getEstado()==TEstadoMotor.Estropeado){
                    sleep(3000); // tiempo de arreglar el motor
                    Motor2.setEstado(TEstadoMotor.Parado);
                    System.out.println("%%%% SE HA ARREGLADO EL MOTOR 2 %%%%");
                }
                sleep(1000);
            } catch (InterruptedException ex) {
                Logger.getLogger(TipoReloj.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

class TipoEstropearMotor extends Thread{
    private TipoMotor Motor1;
    private TipoMotor Motor2;
    
    public TipoEstropearMotor(TipoMotor Motor1, TipoMotor Motor2){
        this.Motor1=Motor1;
        this.Motor2=Motor2;
    }
    
    @Override
    public void run(){
        Random rd = new Random();
        int rand=0;
        while(true){
            try {
                // Decidimos aleatoriamente si se extropea o no
                rand = rd.nextInt(4);
                switch (rand){
                    case 1: // Estropeamos el motor1
                        if (Motor1.getEstado() != TEstadoMotor.Parado) // Parado no se estropea el motor
                            Motor1.Estropear();
                        break;
                    case 3: // Estropeamos el motor2
                        if (Motor2.getEstado() != TEstadoMotor.Parado) // Parado no se estropea el motor
                            Motor2.Estropear();
                        break;
                }
                sleep(30000);
            } catch (InterruptedException ex) {
                Logger.getLogger(TipoEstropearMotor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

class TipoPersona extends Thread{
    int id;
    TipoSensor_Der Sensor1_Der;
    TipoSensor_Izq Sensor1_Izq;
    TipoSensor_Der Sensor2_Der;
    TipoSensor_Izq Sensor2_Izq;
    
    public TipoPersona(int id, TipoSensor_Der Sensor1_Der,TipoSensor_Izq Sensor1_Izq ,TipoSensor_Der Sensor2_Der, TipoSensor_Izq Sensor2_Izq){
        this.id=id;
        this.Sensor1_Der=Sensor1_Der;
        this.Sensor1_Izq=Sensor1_Izq;
        this.Sensor2_Der=Sensor2_Der;
        this.Sensor2_Izq=Sensor2_Izq;
    }
    
    @Override
    public void run(){
        boolean pasar=false;
        Random rd = new Random();
        int rand=0;
        while(!pasar){ // Intentamos pasar por un lado sino podemos pasamos por otro
            try {
                // Decidimos aleatoriamente por donde entrar
                rand = rd.nextInt(4);
                switch (rand){
                    case 0: // Entrar por la derecha de la cinta1
                        System.out.println("++> La Persona "+id+" quiere entrar a la Cinta1 por la derecha.");
                        pasar=Sensor1_Der.EntrarPersona();
                        if (pasar){
                            System.out.println("--> La Persona "+id+" ha entrado a la Cinta1 por la derecha.");
                            sleep(rand*1000); // Tiempo que se tira en la cinta
                            Sensor1_Der.SalirPersona();
                            System.out.println("//> La Persona "+id+" ha salido de la Cinta1.");
                        }
                        break;
                    case 1: // Entrar por la izquierda de la cinta1
                        System.out.println("++> La Persona "+id+" quiere entrar a la Cinta1 por la izquierda.");
                        pasar=Sensor1_Izq.EntrarPersona();
                        if (pasar){
                            System.out.println("--> La Persona "+id+" ha entrado a la Cinta1 por la izquierda.");
                            sleep(rand*1000); // Tiempo que se tira en la cinta
                            Sensor1_Izq.SalirPersona();
                            System.out.println("//> La Persona "+id+" ha salido de la Cinta1.");
                        }
                        break;
                    case 2: // Entrar por la derecha de la cinta2
                        System.out.println("++> La Persona "+id+" quiere entrar a la Cinta2 por la derecha.");
                        pasar=Sensor2_Der.EntrarPersona();
                        if (pasar){
                            System.out.println("--> La Persona "+id+" ha entrado a la Cinta2 por la derecha.");
                            sleep(rand*1000); // Tiempo que se tira en la cinta
                            Sensor2_Der.SalirPersona();
                            System.out.println("//> La Persona "+id+" ha salido de la Cinta2.");
                        }
                        break;
                    case 3: // Entrar por la izquierda de la cinta2
                        System.out.println("++> La Persona "+id+" quiere entrar a la Cinta2 por la izquierda.");
                        pasar=Sensor2_Izq.EntrarPersona();
                        if (pasar){
                            System.out.println("--> La Persona "+id+" ha entrado a la Cinta2 por la izquierda.");
                            sleep(rand*1000); // Tiempo que se tira en la cinta
                            Sensor2_Izq.SalirPersona();
                            System.out.println("//> La Persona "+id+" ha salido de la Cinta2.");
                        }
                        break;
                }
                sleep(10000); // Tiempo de cambiar de lado
            } catch (InterruptedException ex) {
                Logger.getLogger(TipoEstropearMotor.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}
